﻿/**
 @file Dbc.cpp
 @brief CLite_Dbc 소스 파일.
*/

#include "StdAfx.h"
#include "Lite_Dbc.h"
#include "Lite_Stmt.h"

/**
 @brief 기본 생성자로서, 자신의 인덱스를 기억하고, 초기화 작업 수행.
*/
CLite_Dbc::CLite_Dbc(const int nMyIndex /**< 외부에서 사용되는 인덱스 */)
{
	/// [1]자신의 인덱스 저장.
	m_nMyIndex = nMyIndex;
	/// [2]소켓 초기화.
	m_sock = INVALID_SOCKET;
	/// [3]STMT 변수 초기화.
	for( int i = 0 ; i < STMT_COUNT ; i++)
	{
		m_pStmt[i] = NULL;	
	}
}

/**
 @brief 기본 파괴자로서, 연결 작업 해제와 메모리 해제.
*/
CLite_Dbc::~CLite_Dbc(void)
{
	DisconnectDB();
}

/**
 @brief 소켓의 값을 비교하여, 접속 여부를 판단한다.
*/
BOOL CLite_Dbc::IsConnected()
{
	if ( INVALID_SOCKET == m_sock)
		return FALSE;
	else
		return TRUE;
}

/**
 @brief 소켓으로 데이터를 보낸다. \n
 Loop를 돌면서, 정해진 사이즈를 모두 보낸다.
*/
BOOL  CLite_Dbc::SEND(SOCKET sock, char * buf, const int nBufSize)                            
{
	int nSendSize = 0;
	int nFillSize = 0;
	//int nErr = 0;
	while(TRUE)
	{
		nSendSize = send(sock,buf + nFillSize ,nBufSize - nFillSize,0);
		//INSINT(nSendSize);
		switch(nSendSize)
		{
		//==================================================
		/// [1]소켓 에러일 경우, WSAEWOULDBLOCK 일경우를 제외 하고는 FALSE리턴 한다.
		//==================================================
		case SOCKET_ERROR:
			//nErr = WSAGetLastError();
			//INSINT(nErr);
			if( WSAGetLastError() == WSAEWOULDBLOCK)
			{
				Sleep(100);
				continue;
			}
			else
			{
				return FALSE;
			}
		//==================================================
		/// [2]접속이 끊겼을 경우에는 FALSE를 리턴한다.
		//==================================================
		case 0:
			{
				CLOSESOCKET(m_sock);
				return FALSE;
			}
		//==================================================
		/// [3]데이터를 계속 보낸다. 일정 사이즈 이상 보냈을 경우에는 TRUE를 리턴 한다.
		//==================================================
		default:
			nFillSize += nSendSize;
			if( nFillSize >= nBufSize)
			{
				return TRUE;
			}
		}
	}
	return TRUE;
}					

/**
 @brief 소켓에서 데이터를 읽는다. \n
 Loop를 돌면서, 정해진 사이즈만큼 데이터를 읽는다.
*/
BOOL CLite_Dbc::RECV(SOCKET sock, char * buf, const int nBufSize)                            
{
	int nRecvSize = 0;
	int nFillSize = 0;
	int nErr = 0;
	while(TRUE)
	{
		nRecvSize = recv(sock,(char*)buf + nFillSize, nBufSize - nFillSize,0);
		//INSINT(nRecvSize);
		switch(nRecvSize)
		{
		//==================================================
		/// [1]소켓 에러일 경우, WSAEWOULDBLOCK 일경우를 제외 하고는 FALSE리턴 한다.
		//==================================================
		case SOCKET_ERROR:
			nErr = WSAGetLastError();
			//INSINT(nErr);
			//if( WSAGetLastError() == WSAEWOULDBLOCK)
			if( nErr == WSAEWOULDBLOCK)
			{
				Sleep(100);
				continue;
			}
			else
			{
				return FALSE;
			}
		//==================================================
		/// [2]접속이 끊겼을 경우에는 FALSE를 리턴한다.
		//==================================================
		case 0:
			{
				CLOSESOCKET(m_sock);
				return FALSE;
			}
		//==================================================
		/// [3]데이터를 읽는다. 일정 사이즈 이상 읽었을 경우에는 TRUE를 리턴 한다.
		//==================================================
		default:
			nFillSize += nRecvSize;
			if( nFillSize >= nBufSize)
			{
				return TRUE;
			}
		}
	}
	return TRUE;
}

/**
 @brief 에러 메시지를 설정 한다. 모든 에러 메시지는 이 함수를 사용하여 설정 한다.
*/
void CLite_Dbc::SetErr(const char * pErr)
{
	memset(m_szErr,0,50);
	STRCPY(m_szErr,pErr,50);
}

/**
 @brief 에러 메시지를 설정 리턴 한다.
*/
const char * CLite_Dbc::GetErr()
{
	return m_szErr;
}

/**
 @brief 연결 작업을 수행, 일정 시간/숫자 만큼 Retry.
*/
BOOL CLite_Dbc::ConnectDB(char *pIP, /**< 서버 IP */
	                 int nPort, /**< 서버 Port */
					 int nTimeout, /**< 연결 Time Out */
					 int nRetry, /**< 연결시 재시도 */
					 char *pUID, /**< ID */
					 char * pPWD /**< PASSWORD */)
{
	//==================================================
	/// [1]사용자 정보 및 변수 초기화.
	//==================================================
	memset(m_szIP,'\0',30);
	memset(m_szUID,'\0',30);
	memset(m_szUID,'\0',30);
	m_nPort = nPort;
	m_nTimeout = nTimeout*1000;
	m_nRetry = nRetry;
	strncpy_s(m_szIP,30, pIP ,strlen(pIP)+1);
	strncpy_s(m_szUID,30,pUID,strlen(pUID)+1);
	strncpy_s(m_szPWD,30,pUID,strlen(pPWD)+1);
	m_nAuth = AUTH_NONE;
	//==================================================
	/// [2]연결 확인.
	//==================================================
	if ( INVALID_SOCKET != m_sock)
	{
		SetErr("Already Open");
		return FALSE;
	}
	//==================================================
	/// [3]연결 시도(정해진 숫자 만큼 시도.)
	//==================================================
	BOOL bResult = FALSE;
	for( int i = 0 ; i < m_nRetry ; i ++)
	{
		if( TRUE == TryConnect())
		{
			bResult = TRUE;
			break;
		}
		Sleep(1000);
	}
	if(FALSE == bResult)
		return FALSE;
	//==================================================
	/// [4]로그인 시도.
	//==================================================
	return TryLogIn();
}

/**
 @brief 연관된 STMT 객체를 모두 해제하고, 연결을 해제 한다.
*/
void CLite_Dbc::DisconnectDB()
{
	/// [1] STMT 메모리 해제
	for( int i = 0 ; i < STMT_COUNT ; i++)
	{
		if( NULL != m_pStmt[i])	
		{
			delete m_pStmt[i];
			m_pStmt[i] = NULL;
		}
	}
	/// [2] 소켓 연결 해제
	CLOSESOCKET(m_sock);
}

/**
 @brief STMT의 메모리를 생성하며 또는 미 사용중 메모리 리턴, 최대 카운트를 넘을 경우에는 NULL을 리턴 한다.
 @retrun STMT 포인터.
*/
CLite_Stmt * CLite_Dbc::CreateStmt()
{
	for( int i = 0 ; i < STMT_COUNT ; i ++ )
	{
		//==================================================
		/// [1] 배열이 비어 있을 경우에는 Stmt를 새로 생성 후 포인터 리턴.
		//==================================================
		if( NULL == m_pStmt[i])
		{
			m_pStmt[i] = new CLite_Stmt(i, this, &m_Pack);
			return m_pStmt[i];
		}
		//==================================================
		/// [2] 배열이 생성되어 있고, 사용중이 아니라면, 포인터 리턴.
		//==================================================
		else if (FALSE == m_pStmt[i]->IsUsed()) 
		{
			m_pStmt[i]->SetUsed();
			return m_pStmt[i];
		}
	}
	return NULL;
}

/**
 @brief STMT를 삭제하고 기존의 변수를 사용할 수 있도록 한다.
*/
void  CLite_Dbc::DeleteStmt(CLite_Stmt *pStmt)
{
	for( int i = 0 ; i < STMT_COUNT ; i ++ )
	{
		if( m_pStmt[i] == pStmt)
		{
			delete m_pStmt[i];
			m_pStmt[i] = NULL;
		}
	}
}

/**
 @brief 설정된 IP 및 Port로 연결을 시도 한다.
 @return 연경 성공 여부.
*/
BOOL CLite_Dbc::TryConnect()
{
	//==================================================
	/// [1] 소켓 생성.
	//==================================================
	CLOSESOCKET(m_sock);
	m_sock = socket(AF_INET, SOCK_STREAM, 0);//net_CreateSocket();
	if(INVALID_SOCKET == m_sock)
	{
		SetErr("소켓 생성 에러");
		return FALSE;
	}
	//==================================================
	/// [2] 소켓 변경.
	//==================================================
	if (setsockopt(m_sock,SOL_SOCKET,SO_RCVTIMEO,(char*)&m_nTimeout, sizeof(m_nTimeout)) == SOCKET_ERROR)
	{
		SetErr("Setsockopt Eorr");
		CLOSESOCKET(m_sock);
		return FALSE;
	}
	//==================================================
	/// [3] 연결 시도.
	//==================================================
	SOCKADDR_IN serveraddr;
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(m_nPort);
	serveraddr.sin_addr.s_addr = inet_addr(m_szIP);
	if(  SOCKET_ERROR == connect(m_sock, (SOCKADDR *)&serveraddr,sizeof(serveraddr)))
	{
		CLOSESOCKET(m_sock);
		return FALSE;
	}
	else
	{
		return TRUE;
	}
}

/**
 @brief 설정된 ID와 PWD로 로그인을 수행한다. \n
 로그인정보와 함께 사용 권한도 설정 한다.
*/
BOOL CLite_Dbc::TryLogIn()
{
	LOG_INFO info;
	memset(&info,0,sizeof(LOG_INFO));
	STRCPY(info.szUID,m_szUID,30);
	STRCPY(info.szUID,m_szPWD,30);
	if( FALSE == WriteReadSock(0,ITEM_LOGIN,STAT_END,REPLY_SERVER_OK,sizeof(LOG_INFO),(char*)&info))
		return FALSE;
	memset(&info,0,sizeof(LOG_INFO));
	memcpy(&info,m_Pack.szData,sizeof(LOG_INFO));
	m_nAuth = info.nAuth;
	if( m_nAuth == AUTH_NONE)
		return FALSE;
	return TRUE;
}

/**
 @brief 서버에 데이터를 쓰고 응답을 받으며, 응답의 정합성 체크를 수행 한다.
*/
BOOL CLite_Dbc::WriteReadSock( const short nIndex,    /**< 프로토콜의 순번 */
						  const short nItem,     /**< 프로토콜의 종류 */
						  const short nStat,     /**< 현재 상태      */
						  const short nReply,    /**< 서버의 응답    */
						  const short nDataLen,  /**< 보내려는 데이터의 크기 */
						  const char *pszData    /**< 보내는 데이터 */)
{
	memset(&m_Pack,0, sizeof(PACKET_STRT));
	m_Pack.nIndex = nIndex;
	m_Pack.nItem = nItem;
	m_Pack.nStat = nStat;
	m_Pack.nReply = nReply;
	m_Pack.nDataLen = nDataLen;
	memcpy(m_Pack.szData,pszData,nDataLen);
	//==================================================
	/// [1] 보내기 및 에러 처리.
	//==================================================
	if( FALSE == SEND(m_sock, (char*)&m_Pack,  sizeof(PACKET_STRT)))
	{
		SetErr("Fail To Send.");
		return FALSE;
	}
	//==================================================
	/// [2] 받기 및 에러 처리.
	//==================================================
	if( FALSE == RECV(m_sock,(char*)&m_Pack,sizeof(PACKET_STRT)))
	{
		SetErr("Fail To Read.");
		return FALSE;
	}
	//==================================================
	/// [3] 메시지 해석.
	//==================================================
	if((m_Pack.nItem != nItem) || ( m_Pack.nReply == REPLY_CLIENT_FAIL))
	{
		SetErr(m_Pack.szData);
		return FALSE;
	}
	else
	{
		return TRUE;
	}
}

/**
 @brief 서버에 SQL문 실행을 수행 한다.
 @return 실행 성공 여부.
*/
BOOL CLite_Dbc::Exec_v2(const char * pszSQL /**< SQL 실행문 포인터 */)
{
	if ( 0 >= strlen(pszSQL))
		return FALSE;
	int nRemain = strlen(pszSQL);
	int nPos = 0;
	int nSize = 0;
	int nReply = REPLY_SERVER_INITIAL;
	BOOL  bNew = TRUE;
	short nStat;
	while( nRemain > 0 )
	{
		if( nRemain <= DATA_SIZE )
		{
			nStat = STAT_END;
			nSize = nRemain;
		}
		else if ( TRUE == bNew)
		{
			nStat = STAT_START;
			nSize = DATA_SIZE;
		}
		else
		{
			nStat = STAT_CONTINUE;
			nSize = DATA_SIZE;
		}
		if ( FALSE == WriteReadSock(STMT_COUNT,ITEM_EXECUTE, nStat, nReply,nSize, pszSQL + nPos))
		{
			return FALSE;
		}
		nPos += nSize;
		nRemain -= DATA_SIZE;		
		bNew = FALSE;
		nReply = REPLY_SERVER_OK;
	}
	return TRUE;
}
